home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Freaks Macintosh Archive
/
Freaks Macintosh Archive.bin
/
Freaks Macintosh Archives
/
Textfiles
/
zines
/
DNA
/
DNAV2I2.sit
/
DNAV2I2
/
DNA202.004
< prev
next >
Wrap
Text File
|
1998-03-02
|
42KB
|
846 lines
IBM PC Key Recording
by
Zephyr 6/95
After several months spent having a life, I'm back to do a hacker
article or two. What sparked my interest was the publishing of
the 2600 columns on keyboard recording. While they both mentioned
several nice tips on how to write a keyboard recording program,
neither one of them was particularly detailed, and both offered
the programs at a price. Obviously, being a one time hacker
myself, I wasn't about to pay for anything I could get myself.
Now, I managed to find a couple of key recording programs out
there, some written in assembly, some in C. Quite frankly,
however, none of them worked well enough, or was small enough to
satisfy me. I figured I could write a much better program than
any of the ones currently available. The smallest one I saw came
down to over 1k when assembled into a .COM file. The few that had
source looked like my 'crack baby sister'(tm) wrote them.
Inefficient and bulky to say the least. If I could make my own
version that was smaller and quicker, it would certainly be
better for masking it's effect in the background. Naturally, I
wouldn't want people to notice their machines getting any slower,
or their available RAM suddenly dropping if I installed this on
their machines. After a week of on and off programming, I was
successful. My finished product was an encrypted, stealthy TSR
weighing in at under 512 bytes for the code. In writing this
program I learned a lot about the DOS and the PC itself; that is
what I intend to share with you.
I'm assuming you have some knowledge of the Intel x86 series
of microprocessors; understanding basic data structures like
stacks and buffers is a must. I also assume you have a basic
understanding of assembly language, and the theories behind it.
Without that knowledge, this text will likely be useless to you.
If you're really interested in knowing how to write interesting
programs such as this on your own, I suggest you start learning
low level programming, and come back to this file later.
Here is the typical disclaimer. Obviously, the information
in this article could easily be used to make a program that could
be used in a manner that would break certain laws. I do not
condone illegal activity, and I do not want others to assume that
because they have the tools necessary to break security that they
are somehow obligated to. Anything done with this information is
done at your own risk. Please act with discretion.
First off, I am going to describe how the x86 line of
computers handles code and interrupts so that it will be easier
to explain what I did with it. Obviously, the CPU executes
instructions from memory, and typically it does it in order: one
instruction then the next and so on. Sure, there are jumps and
calls, but they all act like a normal programming language would.
They simply direct the flow of execution within a program.
Another condition exists that changes the flow of execution, and
it has nothing to do with the currently running program. Any time
the CPU gets something called an interrupt, it stops executing
the code it was working on, jumps into an interrupt handler, and
returns to the original code when it's finished running the
handler. The handling routine is often called an ISR(Interrupt
Service Routine). The ISR is a routine that does some sort of
work that needed to be done. When does an interrupt hit the
processor? Most of the hardware interrupts can hit at any time.
For instance, if an interrupt 9h came into the CPU, it would mean
that a key was ready to be read from the keyboard to be put in
the keyboard buffer. Once the interrupt 9 ISR is done putting the
keystroke in the buffer, it returns to the program that was
interrupted. Where are 'interrupts' generated? There are two
sources: it can come from hardware or software. The hardware
interrupts are things like the keyboard controller issuing an
interrupt 9h, to indicate that a key is ready to be read.
Software interrupts are things like DOS call int 21h, which is
the main DOS service call. How does the CPU know where to look
for the ISR routine? It looks at something called the interrupt
vector table(IVT). The IVT has a series of 256 far pointers to
the place in memory where the ISR routines are. If you were to
change any of these pointers, the interrupt would then jump to
another place in memory(which you can then specify). The fact
that you can change the IVT means that you can make your own code
into an ISR. You can make the keyboard interrupt execute code
that you have specified, for instance, AND it will execute only
when a keystroke comes into the computer no matter what your
computer happens to be doing at that moment(reading the HD,
writing to the screen etc.) Once your code has finished doing
it's work, you can call the code that the interrupt originally
wanted to call - the BIOS routine that handles keystrokes.
Basically, you insert yourself as another link in the chain of
events. This makes a lot of sense for our program, since we
really don't want to be executing our key capturing code if
nothing is being input on the keyboard. If we can chain into
interrupts, we can then grab keys only when they're being input.
Because ISR routines can be called at any time, we have to make
sure that our ISR doesn't modify anything that would crash the
computer, such as flags, registers and key data structures used
by DOS or BIOS. I'll address that later.
For our key grabbing interrupt, I decided to chain software
interrupt 16h. This is the BIOS routine that every DOS program
I've ever seen calls to get keyboard input. There is another
possibility here; I could chain into interrupt 9h, which is the
hardware keyboard interrupt. However, it is substantially more
difficult to decipher int 9h, and it's genuinely not necessary
either. Interrupt 16h pulls data out of the keyboard buffer and
gives it in usable form to the program that asked for it.
Interrupt 9h, on the other hand, takes info directly from the
keyboard and puts it into the keyboard buffer for interrupt 16h
to pull back out later. Interrupt 16h gives us the advantage that
we only get the keystrokes that the software asking for
keystrokes got. If we intercepted interrupt 9h, we would get
every keystroke, even if it overran the keyboard buffer or
something else that stopped the program from receiving input. Our
interrupt 16h handler is probably the easiest part of the
program. All it has to do is this: figure out if the interrupt
that's coming in is one of the two which actually pulls keys from
the keyboard buffer(AH=1h or 10h). It either has to execute the
original ISR if it's not one of our two interrupts, or put the
keystroke in the buffer for later saving to disk. Let's try some
pseudocode:
Interrupt 16h is called, and CPU jumps to our code...
Save all registers and flags
IF AH != 0 or 10h
{
Execute old interrupt 16h
Restore all registers and flags
exit
}
ELSE
{
Execute old interrupt 16h
Get returned key value from the interrupt
Get address of the top of the keyboard saving buffer from
variable
Put key value in the buffer
IF buffer is full, call our new int 21h handler to write it to
disk
Increment the top of buffer variable
restore all registers and flags
exit
}
Not difficult at all is it? Your typical ISR routine, which
except for the call if the buffer is full only modifies it's own
memory space. For now, we're not going to deal with the disk
writing call from interrupt 16h, since it requires more depth of
explanation than I can provide just yet. Now, I'm going to get
into the interrupt 21h ISR routine. I'm going to make it so every
time an interrupt 21h comes along, all the keystrokes that have
been saved in the buffer will be written to disk. (Actually I
changed the program later so that you could specify a minimum
number of keystrokes before a save happened. There is a character
input function provided by DOS, and if it's being used by a
program, you'll get a disk write every time you hit a key, which
is not desirable.) The reason I'm chaining into interrupt 21h is
twofold. First off, I don't want my program to write a keystroke
to disk EVERY time a key is pressed, only when something else is
going on in the first place. For instance, if someone types 'dir'
at the commandline, the three letters and the return are put into
the keyboard buffer, waiting to be written to disk. When DOS
tries to search for files, using interrupt 21h, it executes my
code first, thereby writing my keystrokes to disk before doing
the original work the int 21h was supposed to do(search for files
in the current directory). So, this hides the operation of the
keyboard recorder from wary users who might suspect something if
their HD light went on every time they hit a key. The other
reason for chaining interrupt 21h for writing keys to disk is
that it solves many DOS reentrancy issues. One of the most
annoying problems DOS has(besides having a real mode 20 bit
addressing space, and not multitasking) is that it's functions
are not reentrant. They are called serially reusable, in that
they can't be called more than once at the same time. This
normally isn't a problem in your typical program, but for us it
is. From a single program, it's impossible to call interrupt 21h
more than once. However, if you've chained into another interrupt
that stops the CPU in the middle of servicing an interrupt 21h,
and then try to open a file, you'll likely crash your computer,
since you've now entered int 21h twice. I won't go into why you
can't use the DOS interrupts more than once at a time, just know
it's just not a good idea. You will crash your system quickly if
you do. There are ways of calling functions more than once, if
they don't use the DOS stack, or if they're using different DOS
stacks. However, it's difficult to be sure of these things, so I
take a safer method for ensuring there aren't reentrancy
problems. By trapping int 21h, we are assured that we are not
doing file writes during the DOS int 21h ISR, because we get
control before it does. Pseudocode:
Interrupt 21h entrypoint:
Save all registers and flags
IF buffer is empty jump to END
Encrypt keystrokes in buffer
Open file for output
Move file pointer to end of file
Write our keystrokes to file
Close file
Restore all registers and flags
IF we've been called from interrupt 16h, return there
END:
Jump to old interrupt 21h ISR
Exit
Also a very straightforward routine, although when implemented,
it is larger than the much simpler interrupt 16h handler. The
last line is a jump to the old interrupt 21h. We want to jump to
it as the last instruction so it returns directly from the
calling code, and also because we don't have to modify the flags
on the stack. Modifying the flags takes time, and takes up code
size we don't want to spare. In case you're not familiar with how
the stack works in relation to interrupts, let me illustrate:
First of all, a stack works like a stack of plates in a
cafeteria. When you want a plate, the only one you can grab is
the top one. Same with the stack. Although you can save lots of
info on the stack, you can only pull the top piece off. For
instance, if we push three values onto the stack like so:
PUSH A
PUSH B
PUSH C
On the stack it looks like this:
+---------+
| C | <----- Top of stack
+---------+
| B |
+---------+
| A |
+---------+
| |
. .
. .
The last value pushed is the only one available, so to get back
the data back in the right order, you have to pop them off the
stack thusly:
POP C
POP B
POP A
Now that we have the elementary stack theory out of the way,
let's look at how interrupts use the stack. When an interrupt is
called, it jumps to execute ISR code, as we know. But, how does
it know where to return when it's done executing the ISR? An
'IRET' instruction is issued, which returns to the calling code.
Where does it get the info it needs to return? Well the answer is
obviously the stack. When an int instruction is issued, the
processor pushes the current flags onto the stack and then pushes
the current address onto the stack after it:
INT = PUSH FLAGS + PUSH ADDRESS
+---------+
|INT ADDR | <----- Top of stack
+---------+
| FLAGS |
+---------+
| |
. .
. .
This is the data that the IRET instruction needs to return to
where the INT was called in the original code. IRET simply pops
the address off the stack returns there, and then pops the flags
off the stack to put them back to their original conditions.
When we execute a 'CALL' from inside an ISR, it places another
return address onto the stack:
+---------+
|CALL ADDR|
+---------+
|INT ADDR |
+---------+
| FLAGS |
+---------+
| |
. .
. .
Before we execute the IRET instruction, we have to make sure
that we pop all the other data off the top of the stack so that
the INT address is the top frame.
When we jump into the old ISR routine without anything
except the INT ADDR and the FLAGS, the IRET instruction it
executes will take the CPU back to the original code which called
the INT in the first place. Here is a schematic describing the
events that our ISR goes through while it's chained through
interrupts:
-----------------------------------------------------------------
--
MOV AH,40h
INT 21h ; Code executing in the foreground
|
\-------------\ ;The INT 21h looks at the IVT to find
the
| ; interrupt vector for 21h
|
<...--+--+--+--+--+--+--+--+--+--...>
16|17|18|19|20|21|22|23|24|25 ;Interrupt Vector
Table(IVT)
<...--+--+--+--+--+--+--+--+--+--...> ;The IVT points to our ISR
|
|
/-------------/ ; Jumps directly to our ISR before
| ; DOS gets control...
|
Interrupt 21h entrypoint:
Save all registers and flags
IF buffer is empty jump to end
Encrypt keystrokes in buffer
Open file for output
Move file pointer to end of file
Write our keystrokes to file
Close file
IF we've been called from interrupt 16h, return there
end:
Restore all registers and flags
Jump to old interrupt 21h ISR
|
| ; This code is what jumps into the DOS routine
that
| ; actually handles the work to be done by INT 21h
| ; At this point, the stack only has the return
| ; address of the calling code(MOV AH,40h: INT 21h)
|
[DOS INT 21h code]
PUSH DS
..
..
POP DS
IRET ; Here the DOS INT 21h code pulls the original
| ; address of the calling code and returns to
| ; executing the foreground program that called it
|
\------------------------------------------------------\
|
MOV AH,40h |
INT 21h ; Original code executing in the foreground|
JC @write_error <-----------------------------------------/
; System jumps directly back into the calling code
; after the interrupt was called.
-----------------------------------------------------------------
--
Our interrupt 16h handlers works very similarly to this one.
However, with the interrupt 16h handler, we don't have to do save
the flags, because they aren't modified by the BIOS interrupt.
INT 21h does modify the flags(sets carry flag to indicate
failure).
Now that we've figured out the majority of our handler
routines, we have to do a couple more things:
1. Install our handlers in memory
2. Encrypt our code and data to make it less suspicious to the
casual observer.
3. Hide our code in memory so it doesn't show up in normal 'mem'
scans.
4. Make our save file invisible to the user.
Let's figure out first how we install our code. I wrote the
program as a COM file, meaning that there is only one segment to
deal with. Once our code is in memory, all we have to do is leave
it there, but return back to DOS. I do it using int 27h, leaving
the ISR's and the key buffer in memory. Of course, our
installation code also has to redirect the far pointers in the
IVT to point to our new handlers, and it has to save the old ones
so we can insert ourselves into the interrupt chain. You can use
DOS interrupts to
redirect the interrupt table, or you can simply modify it
yourself (my preferred method... Don't use interrupts if it's
just as easy to do the work yourself. Makes debugging of your
code more difficult, and it'll work quicker too.) The IVT is
located at segment 0000. To read or write to a certain interrupt,
simply multiply the interrupt number by 4 and use that as the
offset. For instance, interrupt 16h would turn out to be at
0000:0058h (16h*4). From there it's easy to save the old
interrupt and install the new one. First, move the old interrupt
far pointer into a variable you've defined in your program. Then,
once you have your old interrupt vector saved, install your new
one, pointing to the spot in memory where you have your new ISR.
This gives us control of the interrupts 16h and 21h every time
they're called from now on. When your ISR is called, you simply
execute the code you want (save the keystroke), and pass control
back to the original handler that you have saved in your far
pointer variable.
Now that we know how to install our ISR code, we have to
write a program that will encrypt our code to make it less
obvious. If there's a bunch of file names in the .COM file, it'll
be pretty obvious what files the program deals with. We don't
want our program to be that conspicuous, it'll give away what's
going on way too quickly if it is. For instance, the file name
that the keystrokes will be saved has to be specified explicitly.
Finding it after you know where to look won't take much time,
despite the fact that we hide it from view pretty cleverly later.
So, we'll make it much harder to find the file names in the
executable by encrypting the first portion of the code. Any form
of encryption will do, since all it has to do is make it so the
data we have at the beginning of our program doesn't show to the
average user. A few encryption routines are easy to implement on
the byte level; I chose to NOT each byte. I could have used an
XOR encryption, since it's just as simple, but the code for NOT
is a little easier and smaller. Besides, doing a NOT is exactly
the same as XOR'ing a byte with 0FFh anyway. The NOT instruction
is slightly smaller in memory though, so I used it. Assuming
standard .COM TSR setup, our data is stored at the beginning of
the program, and our installation code is stored near the end, so
I decided to simply encrypt the first half of the program along
with the data. It takes no more code to do that, and it really
doesn't take much more time to decrypt the code before installing
it anyway. It makes disassembling it pretty difficult until you
figure out that it's encrypted. After that though, it shouldn't
be too difficult to pull apart. It will stand up to a
substantially more than casual lookover though. Most sysadmins
and the like won't spend quite the amount of time required to
disassemble the code if they're not even sure about whether a
program is a culprit. A single trivial bit of code can be used to
encrypt both our program, as well as our keystrokes as we save
them to disk.
Now that we have our program encrypted as well as installed
in memory somewhere, we have to find some way to hide it in
memory. After pulling my hair out over this for a while, I
finally came up with a great solution. I'm sure some virus guys
know good tricks for hiding stuff in memory, but I couldn't get
any answers from them. I thought of everything from tacking my
code onto the end of a segment of code in memory owned by another
program. That wouldn't work because if the program wasn't
permanently resident, it would deallocate the memory I wanted and
write over it later, thereby crashing your system. I considered
not actually allocating it. Same problem as before, it would
overwrite my code: crash. I thought about dropping the amount of
available memory using the BIOS data segment. Kinda obvious in
memory scans, and I couldn't be sure that the top of memory would
be free. This works in boot sector loaders, but not after DOS has
loaded. Eventually I started reading DOS internals books until I
found a section describing how DOS actually allocates memory. It
uses a structure called a Memory Control Block (MCB) to assign
memory to different processes. Every single chunk of memory DOS
allocates has an accompanying MCB to describe it. An MCB is a 16
byte (one paragraph structure). It is always located in the
segment before the segment that it describes. For instance if you
allocate memory at segment 0601h, the Memory Control Block which
describes it can be found at segment 0600h.
The first byte of an MCB is either an M or a Z. If it's an
M, it means that it's not the last MCB, where a Z denotes the
last in the MCB chain. For those who know data structures, it's a
singly linked list. This means you can only run the chain one
direction. You have to be able to find the first MCB and trace
your way through it to the end to see them all. You keep reading
MCB's until you hit one that has a Z as the first character. The
second field in the MCB is the owner block. It is the segment
that the MCB belongs to. For a typical block, it points to the
segment directly following it. There are exceptions, but we'll
get to that later. If you're tracing the chain, this is how you
know that a given MCB is pointing to code you own, since the
owner will be equal to your code segment (CS register). We'll
need this info later to find MCB's that we own in memory so we
can modify them to our heart's desire. Next field: the number of
paragraphs in the code segment following it. Each paragraph is 16
bytes long. Using the length field, we can determine the position
in memory of the next Memory Control Block. The unused portion of
the MCB is useless to you unless you want to store interesting
information there. DOS basically leaves it completely alone. The
program name contains an ASCIIZ string of the name of the file
you loaded. It's the easiest way for programs like 'mem' to find
out who a memory block belongs to. I'm going to use it as part of
the way to hide our block.
Typical Memory Control Block:
-----------------------------
Done? Owner Length Unused Program name
------ ------- ------- ------- ------------
1 byte 2 bytes 2 bytes 3 bytes 8 bytes
------ ------- ------- ------- ------------
04Dh 0xxxxh "FILENAME"
Armed with this knowledge, I set out to find how I could
make my code invisible to a user, yet set it as an unusable space
for DOS so it wouldn't be overwritten. It stumped me for about
two days until I decided to find how DOS allocates space for it's
own kernel. I found that there are two ways of making DOS think
that your code is actually part of the operating system. There
are two DOS specifications for OS kernel memory. The first is for
DOS code, and the other is DOS data. The first part of giving our
code to DOS is to mark the owner as number 8. DOS always marks
it's own code as process number 8 for some unknown reason. Then,
in the program name field we put an 'SC' in the first field two
bytes of the program name field to tell DOS that it's a system
code segment. For a Data segment, we put an 'SD' in the first two
bytes. Now, when DOS does memory scans, it won't decide to write
over our code because it's still marked as in use, yet it won't
show up as a separate program, since the DOS kernel is flagged as
a single entity even if there are several segments that are
marked as DOS code.
DOS Memory Control Block:
-------------------------
Done? Owner Length Unused Program name
------ ------- ------- ------- ------------
1 byte 2 bytes 2 bytes 3 bytes 8 bytes
------ ------- ------- ------- ------------
04Dh 0008h 0xxxxh "CS "
This is a sample MCB as it would probably look in memory.
For most programs, two separate segments are created though, and
we want to make sure that we set both of them as DOS code, or
they will show up in a memory scan. The second segment that is
created is the environment block. To make sure that we get all
the segments that are created for our program, we'll simply walk
the entire MCB chain when we start up and mark any MCB's that we
happen to own as owned by DOS instead. Typically, when a program
is loaded by DOS, it's environment block is located directly
before the program segment is, and it would be easy to find by
moving back in memory a few segments. This would certainly be
easier than reading the entire MCB block in sequence, and would
certainly be faster too. The problem with this is when you load a
program high with a 3rd party memory manager such as QEMM or 386-
MAX. They don't guarantee that your environment block will be
located right before your program while in high memory. It will
try to fit programs wherever they can in the HMA. Searching all
the MCB's in order will ensure that we get all the MCB's that are
allocated for us, without depending at all on where they are
located in memory. Very neat solution. If a file is loaded low,
'mem' will show nothing, unless you use the debug option: 'mem
/d':
-----------------------------------------------------------------
-
Conventional Memory Detail:
Segment Total Name Type
------- ---------------- ----------- --------
00000 1,039 (1K) Interrupt Vector
00040 271 (0K) ROM Communication
Area
00050 527 (1K) DOS Communication
Area
00070 2,752 (3K) IO System Data
CON System Device Driver
AUX System Device Driver
PRN System Device Driver
CLOCK$ System Device Driver
A: - D: System Device Driver
COM1 System Device Driver
LPT1 System Device Driver
LPT2 System Device Driver
LPT3 System Device Driver
COM2 System Device Driver
COM3 System Device Driver
COM4 System Device Driver
0011C 5,104 (5K) MSDOS System Data
0025B 10,704 (10K) IO System Data
3,536 (3K) NU-MEGA Installed
Device=S-ICE
1,152 (1K) XMSXXXX0 Installed
Device=HIMEM
1,488 (1K) FILES=30
256 (0K) FCBS=4
512 (1K) BUFFERS=40
624 (1K) LASTDRIVE=G
3,008 (3K) STACKS=9,256
004F8 80 (0K) MSDOS System Program
004FD 64 (0K) COMMAND Data
00501 2,656 (3K) COMMAND Program
005A7 80 (0K) MSDOS -- Free --
005AC 272 (0K) COMMAND Environment
005BD 192 (0K) MSDOS -- Free --
005C9 32,096 (31K) SMARTDRV Program
*00D9F 240 (0K) MSDOS System Program
*00DAE 1,168 (1K) MSDOS System Program
00DF7 240 (0K) MEM Environment
00E06 88,992 (87K) MEM Program
023C0 507,904 (496K) MSDOS -- Free --
Memory Summary:
Type of Memory Total = Used + Free
---------------- ---------- ---------- ----------
Conventional 654,336 57,168 597,168
Upper 0 0 0
Reserved 0 0 0
Extended (XMS) 15,122,432 4,259,840 10,862,592
---------------- ---------- ---------- ----------
Total memory 15,776,768 4,317,008 11,459,760
Total under 1 MB 654,336 57,168 597,168
Memory accessible using Int 15h 0 (0K)
Largest executable program size 596,880 (583K)
Largest free upper memory block 0 (0K)
MS-DOS is resident in the high memory area.
XMS version 3.00; driver version 3.16
* Sections of code that are owned by our keystroke recorder.
Larger section is the code, and the smaller 240 byte block
is the environment block. Still pretty unnoticeable, because
it's attributed to the Operating System.
-----------------------------------------------------------------
-
If we manage to load our code high however, it's completely
invisible to mem even with the debug options on:
-----------------------------------------------------------------
-
Conventional Memory Detail:
Segment Total Name Type
------- ---------------- ----------- --------
00000 1,039 (1K) Interrupt Vector
00040 271 (0K) ROM Communication
Area
00050 527 (1K) DOS Communication
Area
00070 2,752 (3K) IO System Data
CON System Device Driver
AUX System Device Driver
PRN System Device Driver
CLOCK$ System Device Driver
A: - D: System Device Driver
COM1 System Device Driver
LPT1 System Device Driver
LPT2 System Device Driver
LPT3 System Device Driver
COM2 System Device Driver
COM3 System Device Driver
COM4 System Device Driver
0011C 5,104 (5K) MSDOS System Data
0025B 10,272 (10K) IO System Data
1,152 (1K) XMSXXXX0 Installed
Device=HIMEM
3,104 (3K) EMMXXXX0 Installed
Device=EMM386
1,488 (1K) FILES=30
256 (0K) FCBS=4
512 (1K) BUFFERS=40
624 (1K) LASTDRIVE=G
3,008 (3K) STACKS=9,256
004DD 80 (0K) MSDOS System Program
004E2 64 (0K) COMMAND Data
004E6 2,656 (3K) COMMAND Program
0058C 80 (0K) MSDOS -- Free --
00591 272 (0K) COMMAND Environment
005A2 17,056 (17K) SMARTDRV Program
009CC 240 (0K) MEM Environment
009DB 88,992 (87K) MEM Program
01F95 524,960 (513K) MSDOS -- Free --
Upper Memory Detail:
Segment Region Total Name Type
------- ------ -------------- -------- --------------
0C94A 1 14,496 (14K) IO System Data
10,256 (10K) MSCD001 Device=SBPCD
4,192 (4K) CON Device=ANSI
0CD24 3 112 (0K) MSDOS -- Free --
0CD2B 3 15,152 (15K) MOUSE Program
0D0DE 3 17,904 (17K) SHARE Program
0D53D 3 16,432 (16K) MSCDEX Program
0D940 3 192 (0K) MSDOS -- Free --
0D94C 3 16,400 (16K) SMARTDRV Data
0DD4D 3 11,056 (11K) MSDOS -- Free --
Memory Summary:
Type of Memory Total = Used + Free
---------------- ---------- ---------- ----------
Conventional 654,336 40,304 614,032
Upper 91,776 80,416 11,360
Reserved 0 0 0
Extended (XMS)* 15,636,864 4,708,736 10,928,128
---------------- ---------- ---------- ----------
Total memory 16,382,976 4,829,456 11,553,520
Total under 1 MB 746,112 120,720 625,392
Handle EMS Name Size
------- -------- ------
0 060000
1 018000
Total Expanded (EMS) 16,056,320 (15,680K
Free Expanded (EMS)* 11,075,584 (10,816K
* EMM386 is using XMS memory to simulate EMS memory as needed.
Free EMS memory may change as free XMS memory changes.
Memory accessible using Int 15h 0 (0K)
Largest executable program size 613,936 (600K)
Largest free upper memory block 11,056 (11K)
MS-DOS is resident in the high memory area.
XMS version 3.00; driver version 3.16
EMS version 4.00
* You'll notice that our program shows NOWHERE in this listing.
Although it is installed in memory in the UMB's it's not shown!
Pretty handy eh?
-----------------------------------------------------------------
-
You can find the code using MSD even if it's installed high, but
it shows up as 'Excluded UMB area'. Often there are several such
areas besides the ones created by our program, so it blends in
pretty well. Once again, some pretty close scrutiny would be
shrugged off by our program when it's properly installed.
Where are we now? We've got our program decrypted off the
disk, installed in memory, and hidden there from users. What else
is there to do? I've implemented one last step to hide the
effects of our program. I've made it very difficult to find the
file that the recorder uses to save keystrokes to. Since we're
trapping Interrupt 21h anyway, I decided to intercept the
directory interrupts specifically. When a directory scan gets to
the file that we have designated as our keystroke saving program,
it won't display it. What does this mean? It means that you can't
find the file on disk by doing a directory scan. The file still
exists and most file operations work on it. For instance, you can
edit it, delete it, copy it, move it and so on. You simply can't
do any searches for it with normal DOS functions. Using 3rd party
tools(such as Norton Disk-Edit) works for finding it, since they
read the hardware directly with BIOS functions, bypassing DOS
completely. The downside of this is that some disk utility
programs will get upset about the file not showing up. CHKDSK has
no problems with it, and SCANDISK doesn't touch it either. Also,
since I didn't put in any code that checks what directory the
system is currently scanning, if there are files in other
directories with the same filenames as the one you're looking at,
they will be hidden too. For instance, if you're saving
keystrokes to a file named 'data'. If you have another file named
'data' in another directory, it will be invisible too. Because of
this, you should name your save file something obscure that no
other files use. Name it something like 'abzzzzzz.tmp' so it
looks like a DOS temp file, which will go both unnoticed, and is
unlikely to be duplicated elsewhere.
The way we get DOS to miss our file name is by grabbing the
4 interrupts that search for files(11h, 12h, 4Eh, 4Fh.) If those
interrupts are going to find our file name, we simply have DOS
scan the directory for the next file instead. To do this, we have
to find the current DTA, which is where DOS passes back the info
about the current directory scan. Then we have to check to see if
our file name is in there. If it is, we call the FindNextMatch
DOS function instead of returning our file name.
Deploying this program is going to be your toughest
challenge. It's easy to detect it if you know where to look. It
has to be in
your autoexec.bat somewhere if you want it to be loaded every
time that the computer boots. There are a few ways of making your
recorder much more stealthy. The first would be to name it
something that many people use such as 'SETVER' or 'DOSKEY' any
other utility that lots of people might put into their
autoexec.bat. If you want to put it into the Windows directory,
you can throw some text into the program to make it look like
it's actually part of windows. For instance, put some text saying
'Windows Protected Mode', or something about .DLL's.
A better solution would be to make this into a boot sector
program. You can use many of the same techniques that virii do to
hide yourself from sight even better. If it's a boot sector
virus, it actually gets loaded before the Operating System does,
which has it's ups and downs. It means that it's totally
invisible to the user, but it also makes it more difficult to
install the program, since you can't just rely upon DOS
interrupts to allocate memory for your program. Also, if you wish
to revector DOS interrupts, even though you're installing at boot
time, you have to redirect them after DOS is done loading. This
means you have to install your code into memory after BIOS is
done with it's POST(Power On Self Test), but you can't modify the
DOS interrupt vectors until after DOS sets them. I can't go into
DOS boot sector stuff yet, since I haven't gotten it to work on
my system yet. I've crashed my system countless times, but
haven't managed to finish it. I hope you learned a little
something from this article, I certainly learned a lot from
making KEYS.
Shortcomings of KEYS:
1. Doesn't read input in Windows. Windows has it's own keyboard
driver, which bypasses the DOS INT 16h and INT 9h handlers
altogether. To read input in Windows takes much more serious
work. If you're really interested, let me know. Most passwords
and the like are typed in at the commandline anyway(Network
logins etc.) Essentially, any program that does it's own keyboard
work won't let keys capture keystrokes(most arcade style games
won't use int 16)
2. That's about it...
At first I wasn't sure whether I was going to release my
version of KEYS into the general public. However, I decided it's
something lots of people want to have, and it's pretty difficult
to make. It's just a tool, and one that not everyone will have
access to. Also, I included the source so others could learn from
what I did. It doesn't have lots of copyright stuff on it, and
the code has no splash screens or other useless tags saying I
wrote it. So, what you do with it is your choice. I can't, and
don't care about stopping you from claiming it as your own code
or whatever. I only ask that if you use it and find it useful
that you send me some mail telling me how you used it and how
wonderful I am for giving it out. Any feedback I get is great and
makes me more willing to write freeware apps for the public in
the future. That's it, enjoy!
KEYS should work on just about any IBM compatible, as low as
an 8088, and with DOS version 2.0 or higher. Basically any setup
will work out fine for KEYS. On slower systems there might be a
noticeable lag, but I don't know. All I have to test it on are
486's...
Instructions for compiling KEYS:
I wrote KEYS to work with the A86 assembler, version 3.71.
First off, when the assembler is finished assembling the KEYS
source, it's not in encrypted form yet. In order for it to work,
it HAS to be encrypted. If it's not, the loading function will
try to decrypt unencrypted code(bad...) So, you have to run the
program 'ENCODE.EXE' in the same directory as 'KEYS.COM'. It will
spit out the encrypted program 'KEYS2.COM'. If it turns out that
you decide to change KEYS, you may need to change the decryption
program to reflect that. To figure out the length of needed
encryption, simply load up the unencrypted, freshly assembled
version of KEYS and in a debugger. The fourth line of code that's
executed is the length you have to encrypt to. For instance, in
Turbo Debugger, load the program and hit 'F8' 3 times. The line
you're on will be:
MOV CX,[something]
The something is what you have to put as the constant at the top
of 'ENCODE.C'(originally 0x16F.) To configure KEYS, simply run
it's accompanying configuration program, 'KONFIG.EXE' with the
'KEYS.COM' file in the current directory. It'll ask you what the
full path of the save file is you want to use. Konfig works only
on the already encrypted version of KEYS. To decrypt your info,
simply run 'DEC.EXE' with an input file name, and an output file
name.
For instance:
DEC C:\DOS\ABZZABZZ.TMP KEYSTROK.DAT
Keep in mind that you can still access the file when it's on
a system that you can't see it on. For instance, if you're
running it on one of your school computers, you can simply copy
the file from the hard drive to a floppy without decrypting it,
or even being able to see it. So even if your admin sees you
trying to copy a file, he won't be able to figure out where it
is, or even if he gets the file somehow, it's encrypted anyhow.
You're pretty safe here unless you get really stupid, which I
don't discount as a possibility.
That ought to do ya. Good luck with it...
Zephyr